using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

using System.Runtime.Serialization;


namespace XMas
{

    /// <summary>
    /// Base class for all Animation Nodes
    /// </summary>
    [Serializable()]
    abstract public class AnimationNode
    {

        #region Variables

        /// <summary>
        /// Internal ID
        /// </summary>
        protected System.Guid _ID;            // Uniq ID
        /// <summary>
        /// Node name
        /// </summary>
        protected string _strName;
        /// <summary>
        /// Name of property for reflexion
        /// </summary>
        protected string _ReflexionProperty;
        /// <summary>
        /// EDITOR: If true you can't add items to this node
        /// </summary>
        protected bool  _bFixedChildren;
        /// <summary>
        /// EDITOR: Value given by editor
        /// </summary>
        protected float _fEditorValue;
        /// <summary>
        /// Current value of this node
        /// </summary>
        protected float _fCurrentValue;
        /// <summary>
        /// Acumulate weigth of all parents nodes
        /// </summary>
        protected float _fIncomeWeigth;
        /// <summary>
        /// EDITOR: Position X of node
        /// </summary>
        protected int   _iEditorX;
        /// <summary>
        /// EDITOR: Position Y of node
        /// </summary>
        protected int   _iEditorY;
        /// <summary>
        /// List of items
        /// </summary>
        protected List<AnimationNodeItem> _animNodeItemList;
        /// <summary>
        /// List of parents
        /// </summary>
        protected List<AnimationNode>     _parentNodes;
        /// <summary>
        /// INTERNAL: Object used in reflexion
        /// </summary>
        protected object _reflexionObject;
        /// <summary>
        /// EDITOR: Name of the category to show in editor
        /// </summary>
        protected string _CategoryName;

        /// <summary>
        /// Number of version of this node, used in serialization
        /// </summary>
        /// <remarks>
        /// If you add a new value to serialization, you need to increment this float
        /// This is need to have compatibility with olders version of library
        /// <see cref="WriteXml(System.Xml.XmlWriter)"/>
        /// </remarks>        
        protected const float _SerializationVersion = 1.0f;

        #endregion


        # region Events and Delegates

        /// <summary>
        /// EDITOR: Internal delegate to control value changes
        /// </summary>
        /// <param name="AnimNode">Node that change</param>
        public delegate void PropertiesChangesDelegate(AnimationNode AnimNode);
        /// <summary>
        /// EDITOR: Event raised when a property change
        /// </summary>
        public event PropertiesChangesDelegate onPropertiesChanges;

        #endregion


        # region Public Methods

        /// <summary>
        /// EDITOR EVENT: Called by editor when slider move
        /// </summary>
        /// <param name="iNewValue">Value is between 0 and 100</param>
        abstract public void HandleSliderMove(int iNewValue);       

        /// <summary>
        /// Used by system when the node need to update his weight
        /// </summary>
        /// <param name="DeltaSeconds">Elapsed time</param>
        abstract public void UpdateWeights(double DeltaSeconds);

        /// <summary>
        /// Used by system after the node update his weight
        /// </summary>
        /// <param name="elapsedTime">Elapsed</param>
        abstract public void PostUpdateWeights(TimeSpan elapsedTime);

        /// <summary>
        /// Create a conection between this node and another node item
        /// </summary>
        /// <param name="srcNode">Source node</param>
        /// <param name="iIdxNode">Source item of srcNode</param>
        /// <returns></returns>
        public bool CreateNodeConnection(AnimationNode srcNode, int iIdxNode)
        {
            bool bResult = false;

            if (srcNode != null && iIdxNode >= 0 && iIdxNode < srcNode._animNodeItemList.Count)
            {
                AnimationNodeItem item = srcNode._animNodeItemList[iIdxNode];
                if (item != null)
                {
                    // If we have a connection
                    if (item.AnimNodeDestination != null)
                        srcNode.DestroyOutcomeNodeConnection(iIdxNode);

                    // Link
                    item.SetConnection(this);
                    _parentNodes.Add(srcNode);
                    bResult = true;
                }
            }

            return bResult;
        }

        /// <summary>
        /// Destroy outcoming conection of one child
        /// </summary>
        /// <param name="iIdxNode">Index of child</param>
        /// <returns></returns>
        public bool DestroyOutcomeNodeConnection(int iIdxNode)
        {
            bool bResult = false;

            if (iIdxNode >= 0 && iIdxNode < _animNodeItemList.Count)
            {
                AnimationNodeItem item = _animNodeItemList[iIdxNode];
                if (item != null && item.AnimNodeDestination != null)
                {
                    item.AnimNodeDestination._parentNodes.Remove(this);
                    item.SetConnection(null);

                    bResult = true;
                }
            }

            return bResult;
        }

        /// <summary>
        /// Destroy all incoming connections of this node
        /// </summary>
        public void DestroyIncomeNodeConnections()
        {
            // Check parents
            foreach (AnimationNode parentNode in _parentNodes)
            {
                foreach (AnimationNodeItem itemNode in parentNode._animNodeItemList)
                {
                    if (itemNode.AnimNodeDestination == this)
                        itemNode.SetConnection(null);
                }
            }
            _parentNodes.Clear();
        }

        /// <summary>
        /// Add new child to this node
        /// </summary>
        /// <param name="ItemText">Child text</param>
        /// <param name="fInitialWeigth">Child initial weigth </param>
        /// <returns>New item</returns>
        public virtual AnimationNodeItem AddChild(string ItemText, float fInitialWeigth)
        {
            AnimationNodeItem Item = null;
            if (!_bFixedChildren)
            {
                Item = new AnimationNodeItem();
                Item._fLocalWeigth = fInitialWeigth;
                Item.Text = ItemText;
                _animNodeItemList.Add(Item);
            }
            return Item;
        }

        /// <summary>
        /// Remove a child from this node
        /// </summary>
        /// <param name="iIdxNode">Child index </param>
        /// <returns>True if the children has been removed</returns>
        public virtual bool RemoveChild(int iIdxNode)
        {
            bool bResult = false;
            if (!_bFixedChildren && iIdxNode >= 0 && iIdxNode < _animNodeItemList.Count)
            {
                bResult = DestroyOutcomeNodeConnection(iIdxNode);
                _animNodeItemList.RemoveAt(iIdxNode);
            }
            return bResult;
        }

        /// <summary>
        /// Get the value to set in eatch frame (animations) or initial load
        /// </summary>
        /// <returns>Value from 0 to 100</returns>
        public virtual int GetEditorValue()
        {            
            return (int)(_fEditorValue * 100.0f);
        }

        # endregion


        #region Properties

        /// <summary>
        /// Atribute for _ID
        /// <see cref="AnimationNode._ID"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("ID"), Browsable(false)]
        public Guid ID
        {
            get { return _ID; }
        }

        /// <summary>
        /// Atribute for _strName, need to property grid (Editor)
        /// <see cref="AnimationNode._strName"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("Node name")]
        public virtual string Name
        {
            get { return _strName;  }
            set 
            { 
                _strName = value;
                if (onPropertiesChanges != null)
                    onPropertiesChanges(this);
            }
        }

        /// <summary>
        /// Atribute for _ReflexionProperty, need to property grid (Editor)
        /// <see cref="AnimationNode._ReflexionProperty"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("Name of the Property of class used for reflexion")]
        public virtual string ReflexionProperty
        {
            get { return _ReflexionProperty; }
            set { _ReflexionProperty = value; }
        }

        /// <summary>
        /// Atribute for _animNodeItemList
        /// <see cref="AnimationNode._animNodeItemList"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("List of childrens"), Browsable(false)]
        public List<AnimationNodeItem> ItemList
        {
            get { return _animNodeItemList; }            
        }

        /// <summary>
        /// Atribute for _parentNodes
        /// <see cref="AnimationNode._parentNodes"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("Parent Nodes"), Browsable(false)]
        public List<AnimationNode> ParentNodes
        {
            get { return _parentNodes; }
        }

        /// <summary>
        /// Atribute for _bFixedChildren
        /// <see cref="AnimationNode._bFixedChildren"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("If you can't add more childrens"), Browsable(false)]
        public bool FixedChildren
        {
            get { return _bFixedChildren; }
        }

        /// <summary>
        /// Atribute for _fIncomeWeigth
        /// <see cref="AnimationNode._fIncomeWeigth"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("Income acumulation Weigth from all childrens"), Browsable(false)]
        public float IncomeWeigth
        {
            get { return _fIncomeWeigth;  }
            set { _fIncomeWeigth = value; }
        }

        /// <summary>
        /// Atribute for _iEditorX
        /// <see cref="AnimationNode._iEditorX"/>
        /// </summary>
        [Browsable(false)]
        public int EditorX
        {
            get { return _iEditorX; }
            set { _iEditorX = value; }
        }

        /// <summary>
        /// Atribute for EditorY
        /// <see cref="AnimationNode.EditorY"/>
        /// </summary>
        [Browsable(false)]
        public int EditorY
        {
            get { return _iEditorY; }
            set { _iEditorY = value; }
        }

        /// <summary>
        /// Atribute for _reflexionObject
        /// <see cref="AnimationNode._reflexionObject"/>
        /// </summary>
        [Browsable(false)]
        public Object ReflexionObject
        {
            get { return _reflexionObject; }
            set { _reflexionObject = value; }
        }

        /// <summary>
        /// Atribute for _CategoryName, need to property grid (Editor)
        /// <see cref="AnimationNode._CategoryName"/>
        /// </summary>
        [CategoryAttribute("AnimNode"), DescriptionAttribute("Category to group nodes in adding menu"), Browsable(false)]
        public string CategoryName
        {
            get { return _CategoryName;  }
        }
        
        #endregion


        #region Load/Save XML

        /// <summary>
        /// Function to write in XML format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the save order, it must be the same that read order!!
        /// </remarks>
        /// <param name="writer">Writer to store data</param>
        public virtual void WriteXml(XmlWriter writer)
        {
            writer.WriteElementString("SerializationVersion", _SerializationVersion.ToString(new System.Globalization.CultureInfo("en-US")));
            writer.WriteElementString("ID", _ID.ToString());
            writer.WriteElementString("Name", _strName);
            writer.WriteElementString("ReflexionProperty", _ReflexionProperty);
            writer.WriteElementString("EditorValue", _fEditorValue.ToString(new System.Globalization.CultureInfo("en-US")));
            writer.WriteElementString("CurrentValue", _fCurrentValue.ToString(new System.Globalization.CultureInfo("en-US")));
            writer.WriteElementString("EditorX", _iEditorX.ToString());
            writer.WriteElementString("EditorY", _iEditorY.ToString());
            writer.WriteStartElement("ItemList", null);
            // Item list
            foreach (AnimationNodeItem Item in _animNodeItemList)
            {
                writer.WriteStartElement("Item", null);
                Item.WriteXml(writer);
                writer.WriteEndElement();
            }
            writer.WriteEndElement();     
        }

        /// <summary>
        /// Function to read xml format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the read order, it must be the same that save order!!
        /// </remarks>
        /// <param name="reader">Reader to retrieve data</param>
        public virtual void ReadXml(XmlReader reader)
        {
            float SerializationVersion = reader.ReadElementContentAsFloat("SerializationVersion", reader.NamespaceURI);
            _ID = new Guid(reader.ReadElementContentAsString("ID", reader.NamespaceURI));
            _strName = reader.ReadElementContentAsString("Name", reader.NamespaceURI);
            _ReflexionProperty = reader.ReadElementContentAsString("ReflexionProperty", reader.NamespaceURI);
            _fEditorValue = reader.ReadElementContentAsFloat("EditorValue", reader.NamespaceURI);
            _fCurrentValue = reader.ReadElementContentAsFloat("CurrentValue", reader.NamespaceURI);
            _iEditorX = reader.ReadElementContentAsInt("EditorX", reader.NamespaceURI);
            _iEditorY = reader.ReadElementContentAsInt("EditorY", reader.NamespaceURI);
            // Get item list
            _animNodeItemList = new List<AnimationNodeItem>();
            reader.ReadStartElement("ItemList");
            bool bFound = false;
            while (reader.IsStartElement("Item", reader.NamespaceURI))
            {                
                reader.ReadStartElement();
                AnimationNodeItem animItem = new AnimationNodeItem();
                animItem.ReadXml(reader);
                reader.ReadEndElement();
                _animNodeItemList.Add(animItem);
                bFound = true;
            }   
            // </itemlist> only if items found
            if (bFound)
                reader.ReadEndElement();
        }

        #endregion


        # region Protected Members

        /// <summary>
        /// Initialization of node, set initial values
        /// </summary>
        /// <param name="ID">Node ID</param>
        protected virtual void Initialize(System.Guid ID)
        {
            _ID = ID;
            _animNodeItemList = new List<AnimationNodeItem>();
            _strName = "AnimNode";
            _parentNodes = new List<AnimationNode>();
            _bFixedChildren = false;
            _fEditorValue = 0;
            _fCurrentValue = 0;
            _fIncomeWeigth = 0;
            _reflexionObject = null;
            _CategoryName = "Nodes";
        }

        /// <summary>
        /// Internal get current value, first try to use reflexion. If no reflexion found use editor value
        /// Override with caution
        /// </summary>
        /// <returns></returns>
        protected virtual float GetValue()
        {
            // Gets the new node value
            // If we haven't reflexion yet use editor
            float fResultValue = _fEditorValue;
            if (_reflexionObject != null && _ReflexionProperty != null && _ReflexionProperty.Length > 0)
            {
                // Try to get this property
                Type reflexionType = _reflexionObject.GetType();
                PropertyInfo propInfo = reflexionType.GetProperty(_ReflexionProperty);
                if (propInfo != null && propInfo.CanRead)
                {
                    // TODO: Test with enums
                    object resultObjectValue = propInfo.GetValue(_reflexionObject, null);
                    Type dataType = propInfo.PropertyType;
                    if (dataType == typeof(float))
                        fResultValue = (float)resultObjectValue;
                    else if (dataType == typeof(double))
                        fResultValue = (float)(double)resultObjectValue;
                    else if (dataType == typeof(int) || dataType.IsEnum)
                        fResultValue = (float)(int)resultObjectValue;
                }
            }
            return fResultValue;
        }

        # endregion


        # region Public Events

        /// <summary>
        /// Called when node become active, more than 0 weith
        /// </summary>
        public virtual void OnBecomeActive()
        {
        }

        /// <summary>
        /// Called when node become inactive, 0 weith
        /// </summary>
        public virtual void OnBecomeInactive()
        {
        }

        /// <summary>
        /// Called when a AnimNode_Animation child has finish animation
        /// </summary>
        /// <param name="sender">Node that raise event</param>
        public virtual void OnChildAnimEnd(AnimNode_Animation sender)
        {
        }

        #endregion


        # region Constructors

        /// <summary>
        /// Basic constructor with a new ID
        /// </summary>
        public AnimationNode()
        {
            // WithoutID
            Initialize(System.Guid.NewGuid());
        }

        /// <summary>
        /// Constructor with given id
        /// </summary>
        /// <param name="ID">Node ID</param>
        public AnimationNode(System.Guid ID)
        {
            Initialize(ID);
        }

        #endregion


    } 
}
